// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/android/composited_touch_handle_drawable.h"

#include "base/android/context_utils.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/trace_event/trace_event.h"
#include "cc/layers/ui_resource_layer.h"
#include "content/public/browser/android/compositor.h"
#include "jni/HandleViewResources_jni.h"
#include "ui/gfx/android/java_bitmap.h"

using base::android::JavaRef;

namespace content {

namespace {

    static SkBitmap CreateSkBitmapFromJavaBitmap(
        base::android::ScopedJavaLocalRef<jobject> jbitmap)
    {
        return jbitmap.is_null()
            ? SkBitmap()
            : CreateSkBitmapFromJavaBitmap(gfx::JavaBitmap(jbitmap));
    }

    class HandleResources {
    public:
        HandleResources()
            : loaded_(false)
        {
        }

        void LoadIfNecessary(const JavaRef<jobject>& context)
        {
            if (loaded_)
                return;

            loaded_ = true;

            TRACE_EVENT0("browser", "HandleResources::Create");
            JNIEnv* env = base::android::AttachCurrentThread();

            left_bitmap_ = CreateSkBitmapFromJavaBitmap(
                Java_HandleViewResources_getLeftHandleBitmap(env, context));
            right_bitmap_ = CreateSkBitmapFromJavaBitmap(
                Java_HandleViewResources_getRightHandleBitmap(env, context));
            center_bitmap_ = CreateSkBitmapFromJavaBitmap(
                Java_HandleViewResources_getCenterHandleBitmap(env, context));

            left_bitmap_.setImmutable();
            right_bitmap_.setImmutable();
            center_bitmap_.setImmutable();

            drawable_horizontal_padding_ratio_ = Java_HandleViewResources_getHandleHorizontalPaddingRatio(env);
        }

        const SkBitmap& GetBitmap(ui::TouchHandleOrientation orientation)
        {
            DCHECK(loaded_);
            switch (orientation) {
            case ui::TouchHandleOrientation::LEFT:
                return left_bitmap_;
            case ui::TouchHandleOrientation::RIGHT:
                return right_bitmap_;
            case ui::TouchHandleOrientation::CENTER:
                return center_bitmap_;
            case ui::TouchHandleOrientation::UNDEFINED:
                NOTREACHED() << "Invalid touch handle orientation.";
            };
            return center_bitmap_;
        }

        float GetDrawableHorizontalPaddingRatio() const
        {
            DCHECK(loaded_);
            return drawable_horizontal_padding_ratio_;
        }

    private:
        SkBitmap left_bitmap_;
        SkBitmap right_bitmap_;
        SkBitmap center_bitmap_;
        float drawable_horizontal_padding_ratio_;
        bool loaded_;

        DISALLOW_COPY_AND_ASSIGN(HandleResources);
    };

    base::LazyInstance<HandleResources>::Leaky g_selection_resources;

} // namespace

CompositedTouchHandleDrawable::CompositedTouchHandleDrawable(
    cc::Layer* root_layer,
    float dpi_scale,
    const JavaRef<jobject>& context)
    : dpi_scale_(dpi_scale)
    , orientation_(ui::TouchHandleOrientation::UNDEFINED)
    , layer_(cc::UIResourceLayer::Create())
{
    g_selection_resources.Get().LoadIfNecessary(context);
    drawable_horizontal_padding_ratio_ = g_selection_resources.Get().GetDrawableHorizontalPaddingRatio();
    DCHECK(root_layer);
    root_layer->AddChild(layer_.get());
}

CompositedTouchHandleDrawable::~CompositedTouchHandleDrawable()
{
    DetachLayer();
}

void CompositedTouchHandleDrawable::SetEnabled(bool enabled)
{
    layer_->SetIsDrawable(enabled);
    // Force a position update in case the disabled layer's properties are stale.
    if (enabled)
        UpdateLayerPosition();
}

void CompositedTouchHandleDrawable::SetOrientation(
    ui::TouchHandleOrientation orientation,
    bool mirror_vertical,
    bool mirror_horizontal)
{
    DCHECK(layer_->parent());
    bool orientation_changed = orientation_ != orientation;

    orientation_ = orientation;

    if (orientation_changed) {
        const SkBitmap& bitmap = g_selection_resources.Get().GetBitmap(orientation);
        const int bitmap_height = bitmap.height();
        const int bitmap_width = bitmap.width();
        layer_->SetBitmap(bitmap);
        layer_->SetBounds(gfx::Size(bitmap_width, bitmap_height));
    }

    const int layer_height = layer_->bounds().height();
    const int layer_width = layer_->bounds().width();

    // Invert about X and Y axis based on the mirror values
    gfx::Transform transform;
    float scale_x = mirror_horizontal ? -1.f : 1.f;
    float scale_y = mirror_vertical ? -1.f : 1.f;

    layer_->SetTransformOrigin(
        gfx::Point3F(layer_width * 0.5f, layer_height * 0.5f, 0));
    transform.Scale(scale_x, scale_y);
    layer_->SetTransform(transform);
}

void CompositedTouchHandleDrawable::SetOrigin(const gfx::PointF& origin)
{
    origin_position_ = gfx::ScalePoint(origin, dpi_scale_);
    UpdateLayerPosition();
}

void CompositedTouchHandleDrawable::SetAlpha(float alpha)
{
    DCHECK(layer_->parent());
    alpha = std::max(0.f, std::min(1.f, alpha));
    bool hidden = alpha <= 0;
    layer_->SetOpacity(alpha);
    layer_->SetHideLayerAndSubtree(hidden);
}

gfx::RectF CompositedTouchHandleDrawable::GetVisibleBounds() const
{
    return gfx::ScaleRect(gfx::RectF(layer_->position().x(),
                              layer_->position().y(),
                              layer_->bounds().width(),
                              layer_->bounds().height()),
        1.f / dpi_scale_);
}

float CompositedTouchHandleDrawable::GetDrawableHorizontalPaddingRatio() const
{
    return drawable_horizontal_padding_ratio_;
}

void CompositedTouchHandleDrawable::DetachLayer()
{
    layer_->RemoveFromParent();
}

void CompositedTouchHandleDrawable::UpdateLayerPosition()
{
    layer_->SetPosition(origin_position_);
}

} // namespace content
